package com.lianjinsu.shop.core.configuration;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

@Slf4j
@Configuration
public class RestTemplateConfiguration {
    @Bean
    RestTemplate restTemplate(RestTemplateBuilder builder, MyClientHttpRequestInterceptor interceptor) {
        // 忽略安全证书
        TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
        SSLContext sslContext = null;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        Objects.requireNonNull(sslContext);

        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        NoopHostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
        httpClientBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext, hostnameVerifier));
        httpClientBuilder.setMaxConnPerRoute(16);
        httpClientBuilder.setMaxConnTotal(256);
        httpClientBuilder.setRetryHandler(new MyHttpRequestRetryHandler());
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setHttpClient(httpClientBuilder.build());
        httpRequestFactory.setConnectTimeout(120000);
        httpRequestFactory.setConnectionRequestTimeout(120000);
        httpRequestFactory.setReadTimeout(180000);

        builder = builder.additionalInterceptors(interceptor);
        builder = builder.requestFactory(() -> httpRequestFactory);
        RestTemplate restTemplate = builder.build();
        // 先获取到converter列表
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> converter : converters) {
            // 因为我们只想要jsonConverter支持对text/html的解析
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                try {
                    // 先将原先支持的MediaType列表拷出
                    List<MediaType> mediaTypeList = new ArrayList<>(converter.getSupportedMediaTypes());
                    // 加入对text/html的支持
                    mediaTypeList.add(MediaType.TEXT_HTML);
                    // 将已经加入了text/html的MediaType支持列表设置为其支持的媒体类型列表
                    ((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
                } catch (Exception e) {
                    log.error(e.getLocalizedMessage(), e);
                }
            }
        }
        return restTemplate;
    }

    /**
     * 修改重试异常的类型
     */
    private static class MyHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
        /**
         * 这几类异常不重试，可根据需要修改异常集合
         */
        private final static Set<Class<? extends IOException>> NON_RETRIABLE_CLASSES = new HashSet<>(8, 1);

        static {
            NON_RETRIABLE_CLASSES.add(UnknownHostException.class);
            NON_RETRIABLE_CLASSES.add(ConnectException.class);
            NON_RETRIABLE_CLASSES.add(NoRouteToHostException.class);
            NON_RETRIABLE_CLASSES.add(SSLException.class);
        }

        private MyHttpRequestRetryHandler() {
            super(3, true, NON_RETRIABLE_CLASSES);
        }
    }

    @Component
    static class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
                throws IOException {
            StopWatch stopWatch = new StopWatch(MyClientHttpRequestInterceptor.class.getSimpleName());
            stopWatch.start();
            ClientHttpResponse response = execution.execute(request, body);
            stopWatch.stop();
            log.info("this request cost: {}, uri: {}", stopWatch.getTotalTimeMillis(), request.getURI());
            return response;
        }
    }
}
